[小ネタ] [fp-ts] TaskEitherで条件付きリトライを実装する
はじめに
前回の記事の続きの小ネタとなりますが、taskEitherで条件付きリトライを実装してみたのでそのメモです。
指針
以下のようにTaskEitherとリトライ条件の判定関数を受け取ってリトライ付きで実行する関数を実装してみます。
このシグネチャにすることでWeb APIのレスポンスやDBエラーを柔軟にハンドリングでき、リトライ処理を透過的に追加することができます。
function retry <E, R>(fa: () => TaskEither<E, R>, predicate: (e:E => boolean)):TaskEither<E, R>
leftFlatMap
まずはEitherのLeft側だけをflatMapする以下のヘルパーを定義します。
import { taskEither as TE } from 'fp-ts' import { TaskEither } from 'fp-ts/lib/TaskEither' export const leftFlatMap = <R, A, B>(f: (a: A) => TaskEither<B, R>) => (fa: TaskEither<A, R>): TaskEither<B, R> => TE.fold( (a:A) => f(a), (r:R) => TE.right(r) )(fa)
retry
上記を使って実装すると下記のようになります。
import { TaskEither, fromIOEither, left } from 'fp-ts/lib/TaskEither' function retry<E, A> (fa: () => TaskEither<E, A>, predicate: (e: E) => boolean): TaskEither<E, A> { return pipeable.pipe( fa(), leftFlatMap(e => predicate(e) ? retry(fa, predicate) : left(e)) ) }
使ってみます
import { fromIOEither } from 'fp-ts/lib/TaskEither' import { leftFlatMap } from './util/flatmap' import { pipeable, either } from 'fp-ts' const Retry = 'Retry' const Abort = 'Fail' type Fail = typeof Retry | typeof Abort let count = 0 const t = () => fromIOEither(() => { console.log(`Retry count ${count}`) count++ return either.left<Fail, string>(count >= 10000 ? Fail : Retry) }) retry(t, e => e === Retry)().then(console.log) // Retry count 0 // Retry count 1 // Retry count 2 // . // . // . // Retry count 9999 // { _tag: 'Left', left: 'Abort' }
retry-ts
実装してから気づいたのですが、ほぼ同じインターフェースでバックオフによるスリープを追加したretry-tsライブラリがありました。こちらを使ったほうがいいと思います・・・。
まとめ
fp-tsでリトライ処理を実装してみました。